﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Drawing;
using System.Linq;
using System.Web;
using Eksponent.CropUp.Config;
using ImageResizer;
using ImageResizer.Configuration;
using ImageResizer.Plugins;
using ImageResizer.Resizing;
using ImageResizer.Util;

namespace Eksponent.CropUp.Plugins
{
    public class ImageResizerPlugin : BuilderExtension, IPlugin, IQuerystringPlugin
    {       
        public CropUp CropUp { get; set; }

        public ImageResizerPlugin()
        {
            CropUp = new CropUp();            
        }

        public ImageResizerPlugin(CropUp cropUp)
        {
            CropUp = cropUp;
            //new CropUpPlugin().Install(ImageResizer.Configuration.Config.Current);
        }


        public IEnumerable<string> GetSupportedQuerystringKeys()
        {
            return new[] { "cropUpGravity", "cropUpBox", "cropUpAlias", "cropUpAbsolute", "cropUpProviderData", "cropUpReferenceWidth", "cropUpReferenceHeight", "cropUpZoom", "cropUpClientMode" };
        }

        public IPlugin Install(ImageResizer.Configuration.Config c)
        {
            c.Plugins.add_plugin(this);
            c.Pipeline.Rewrite += Pipeline_Rewrite;
            return this;
        }

        public bool Uninstall(ImageResizer.Configuration.Config c)
        {
            c.Plugins.remove_plugin(this);
            c.Pipeline.Rewrite -= Pipeline_Rewrite;
            return true;
        }

        void Pipeline_Rewrite(IHttpModule sender, HttpContext context, IUrlEventArgs e)
        {            
            var provider = CropUp.CropDataProvider;
            if (provider != null)
            {
                if (e.QueryString["mode"] == "crop-up" || GetSupportedQuerystringKeys().Any(k => e.QueryString[k] != null))
                {
                    
                    var cropData = provider.GetCropData(e.VirtualPath);
                    if (cropData != null)
                    {
                        var cropInfo = cropData.Default ?? CropInfo.Default;
                        cropInfo.Calculated = true;
                        var cropAlias = e.QueryString["cropUpAlias"];
                        if (cropAlias != null)
                        {
                            if (cropData.ManualCroppings != null &&
                                cropData.ManualCroppings.ContainsKey(cropAlias))
                            {
                                cropInfo = cropData.ManualCroppings[cropAlias];
                                cropInfo.Calculated = false;
                            }
                        }

                        e.QueryString["cropUpProviderData"] = JsonHelper.Serializer.Serialize(cropInfo);
                    }
                }
            }

        }

        protected override RequestedAction LayoutImage(ImageState s)
        {
            if (s.settings["mode"] == "crop-up")
            {
                int width, height;

                var cropAlias = s.settings["cropUpAlias"];

                // If these properties are not specified no CropUp action is to be taken.
                if (string.IsNullOrEmpty(cropAlias) &
                    !int.TryParse(s.settings["width"], out width) &
                    !int.TryParse(s.settings["height"], out height))
                {
                    return RequestedAction.None;
                }

                var size = s.originalSize;

                var cropInfo = !string.IsNullOrEmpty(s.settings["cropUpProviderData"]) ?
                    JsonHelper.Serializer.Deserialize<CropInfo>(s.settings["cropUpProviderData"])
                    : CropInfo.Default;


                if (!string.IsNullOrEmpty(s.settings["cropUpGravity"]) || !string.IsNullOrEmpty(s.settings["cropUpBox"]))
                {
                    if (s.settings["cropUpAbsolute"] == "true")
                    {
                        cropInfo = cropInfo.ToAbsolute(size.Width, size.Height);
                    }
                    else
                    {
                        cropInfo = cropInfo.ToRelative(size.Width, size.Height);
                    }

                    if (!string.IsNullOrEmpty(s.settings["cropUpGravity"]))
                    {                                                        
                        cropInfo.Gravity = Utils.parsePointF(s.settings["cropUpGravity"], new PointF(0, 0));
                    }

                    if (!string.IsNullOrEmpty(s.settings["cropUpBox"]))
                    {
                        var coords = Utils.parseList(s.settings["cropUpBox"], 0);
                        cropInfo.TopLeft = new PointF((float)coords[0], (float)coords[1]);
                        cropInfo.BottomRight = new PointF((float)coords[2], (float)coords[3]);
                    }
                }

                if (!string.IsNullOrEmpty(cropAlias))
                {
                    var def = ManualCroppings.FirstOrDefault(x => x.Alias == cropAlias);
                    if (def != null && cropInfo.Calculated)
                    {
                        cropInfo = CropUp.Suggest(size.Width, size.Height, def.Width, def.Height, cropInfo);
                    }
                }
                if (!string.IsNullOrEmpty(cropAlias) && width == 0 && height == 0)
                {
                    var def = ManualCroppings.FirstOrDefault(x => x.Alias == cropAlias);
                    if (def != null)
                    {
                        width = def.Width;
                        height = def.Height;
                    }
                }

                var refWidth = ParseInt(s.settings["cropUpReferenceWidth"]);
                var refHeight = ParseInt(s.settings["cropUpReferenceHeight"]);

                var allowZoom = s.settings["cropUpZoom"] == "true";

                var cropping = CropUp.GetCropping(size.Width, size.Height,
                    width == 0 ? (int?)null : width,
                    height == 0 ? (int?)null : height,
                    cropInfo, resizeToCropping: !string.IsNullOrEmpty(cropAlias), referenceWidth: refWidth, referenceHeight: refHeight, allowZoom: allowZoom);


                #region Handler for client-side responsive images
                //Does the opposite of maxWidth and maxHeight.
                if (s.settings["cropUpClientMode"] == "true")
                {
                    var zoom = 2 - (cropping.BottomRight.X - cropping.TopLeft.X) / ((double)s.originalSize.Width);
                    zoom = Math.Min(zoom, 2 - (cropping.BottomRight.Y - cropping.TopLeft.Y) / ((double)s.originalSize.Height));

                    width = (int)Math.Round(width * zoom);
                    height = (int)Math.Round(height * zoom);

                    var aspect = (double)s.originalSize.Width / s.originalSize.Height;

                    s.settings.Height = s.settings.Width = 0;
                    while (s.settings.Width < width || s.settings.Height < height)
                    {

                        if (s.settings.Width < width)
                        {
                            s.settings.Width = width;
                            s.settings.Height = (int)Math.Floor(width / aspect);
                        }
                        else
                        {
                            s.settings.Height = height;
                            s.settings.Width = (int)Math.Floor(height * aspect);
                        }
                    }
                    return RequestedAction.None;
                }
                #endregion

                s.settings.Scale = allowZoom ? ScaleMode.Both : ScaleMode.DownscaleOnly;

                s.settings.Width = cropping.TargetWidth;
                s.settings.Height = cropping.TargetHeight;
                s.settings.CropTopLeft = cropping.TopLeft;
                s.settings.CropBottomRight = cropping.BottomRight;

                //To avoid subpixel white padding caused by rounding errors allow to stretch < 1 pixel
                //Manual croppings are shown entirely so when a crop alias is specified padding is used.
                s.settings.Mode = cropAlias != null ? FitMode.Pad : FitMode.Stretch;
            }
            return RequestedAction.None;
        }

        static int? ParseInt(string s)
        {
            int v;
            return int.TryParse(s, out v) ? (int?)v : null;
        }

        private static List<CropInfoElement> _manualCroppings;
        public static IList<CropInfoElement> ManualCroppings
        {
            get
            {
                return _manualCroppings = (_manualCroppings ?? CropUp.Configuration.CropInfos.Cast<CropInfoElement>().ToList());
            }
        }        
    }
}